Skip to content

Implement PoCW consensus mechanism for ordinary transfers - #15

Open
ABresting wants to merge 12 commits into
dev-aureliafrom
dev-abhi-pocw
Open

Implement PoCW consensus mechanism for ordinary transfers#15
ABresting wants to merge 12 commits into
dev-aureliafrom
dev-abhi-pocw

Conversation

@ABresting

@ABresting ABresting commented Feb 14, 2026

Copy link
Copy Markdown
Collaborator

Summary

Ordinary transfers in the Setu consensus pipeline do not produce causally meaningful work. There is no solver competition, no quality variance, and no downstream computational impact that PoCW can evaluate. The three PoCW scoring signals (distance, necessity, contribution) degenerate when applied to transfers: any correct executor produces an identical result, so there is no meaningful basis for differentiated reward distribution. Given this, the most appropriate economic treatment for ordinary transfers is a flat fee, configurable via PoCWConfig.transfer_fee (default 21,000), that is deducted from the sender's coin alongside the transfer amount and permanently destroyed, reducing total supply. Fee enforcement occurs within the TEE execution environment: the runtime withdraws amount + fee atomically, and if the combined total exceeds available balance, the transfer produces zero state changes. This was validated at precise boundary conditions against live validator runs.

A fold-time economic observer has been introduced into the consensus pipeline to handle the reward side for solver-executed work. At each fold, the observer aggregates per-event burn and power consumption metrics, then computes per-solver PoCW scores across three DAG-derived signals: depth contribution (position of solver events within the causal chain), necessity (fraction of solver events that are ancestors of the fold's tip events), and gas usage (TEE-attested computational effort). These scores are normalized and used to distribute minted Flux proportionally to participating solvers. Cumulative rewards are persisted in the ROOT subnet's sparse Merkle tree under pocw:reward:{solver_id} keys, and each event now carries an executed_by field to distinguish the executing solver from the originating validator.

The entire economic layer is gated behind PoCWConfig.enabled. When disabled, the fee defaults to zero, no fold observer executes, no rewards are minted, and all existing behaviour remains unchanged. Configuration is read from environment variables (POCW_ENABLED, POCW_TRANSFER_FEE) via the dotenv support introduced in the companion PR #13. Naming conventions were standardised across the codebase: burn_fee was renamed to fee as the burn mechanism is an implementation detail, and transfer_fixed_fee was simplified to transfer_fee.

Closes #8

Test plan

  • 15 unit tests pass: flux_burn (3), fold_observer (3), power (3), processor (6)
  • Live binary: fee enforcement at exact boundary (amount + fee > balance produces 0 events processed)
  • PoCW defaults to disabled; all existing tests pass with it off
  • Full workspace: 105/106 pass (1 pre-existing failure unrelated to PoCW)

FoldObserver hooks into ConsensusManager after fold commit to compute
economic summaries. Sets executed_by on events in tee_executor.
Transfer.burn_fee flows from validator through enclave to runtime.
Sender pays amount + burn_fee; recipient gets only amount.
Burned flux is destroyed (supply reduction). Zero burn_fee preserves
existing behavior.
After fold commit, FoldObserver computes economics and apply_reward_minting
writes cumulative rewards to ROOT subnet SMT under "pocw:reward:{solver_id}"
keys. Rewards accumulate across folds. Adds get_value() to GlobalStateManager
for reading state by key.
)

Thread transfer_burn_fee from PoCWConfig through NetworkServiceConfig
to TransferHandler so every FluxTransfer carries the configured burn
fee. When PoCW is disabled (default), burn_fee remains 0 — no change
in behavior.
@ABresting ABresting changed the title Implement PoCW economic layer for transfers Implement PoCW consensus mechanism for ordinary transfers Feb 14, 2026
@ABresting ABresting self-assigned this Feb 14, 2026
ivan-xin added a commit to ivan-xin/Setu that referenced this pull request Apr 22, 2026
…e_df_oid

types (new dynamic_field module):
- DfFieldValue { name_type_tag, name_bcs, value_type_tag, value_bcs }
  — payload persisted in ObjectEnvelope.data for DF entries (ownership =
  ObjectOwner(parent)); BCS-serializable, serde Debug/Clone/Eq
- DfAccessMode enum (Read/Mutate/Create/Delete) for client-declared intent;
  JSON shape locked via unit test
- derive_df_oid(parent, name_type_tag, name_bcs) -> ObjectId
  — BLAKE3(b"SETU_DF:" || parent || type_tag || 0x00 || bcs); canonical
  type tag STRING rather than &TypeTag so types/ keeps its single
  internal dep on setu-vlc (G4). VM-side callers pass
  TypeTag::to_canonical_string().as_str(). See design.md §3.2 +
  implementation-log.md §2 drift note.
- 8 unit tests: deterministic, parent-sensitive, type-tag-sensitive,
  bcs-sensitive, empty-bcs edge, preimage-layout lock,
  DfFieldValue bcs roundtrip, DfAccessMode JSON shape

types (solver_task.rs — ResolvedInputs extension):
- ResolvedDynamicField { parent_object_id, df_object_id, name_type_tag,
  name_bcs, value_bytes: Option<Vec<u8>>, value_type_tag, mode }
- ResolvedInputs.dynamic_fields: Vec<ResolvedDynamicField> with
  #[serde(default)] — top-level per v1.3 §3.5 (NOT nested inside
  OperationType::MoveCall) so future operations can adopt DF without a
  breaking enum layout change
- All 6 constructors (new / transfer / merge_coins / split_coin /
  merge_then_transfer / move_call) initialize dynamic_fields: Vec::new()
- 3 unit tests: serde legacy-payload compat (default empty),
  ResolvedDynamicField bcs roundtrip (Create + Read modes),
  ResolvedInputs.dynamic_fields roundtrip

setu-framework/sources/dynamic_field.move (new stdlib module AdvaitaLabs#15):
- 5 public funs: add / remove / borrow / borrow_mut / exists_
- 5 native forward-declarations (runtime lives in M2)
- 6 error codes: E_FIELD_ALREADY_EXISTS=0, E_FIELD_DOES_NOT_EXIST=1,
  E_FIELD_TYPE_MISMATCH=2, E_DF_VALUE_HAS_KEY=3, E_DF_NOT_PRELOADED=100,
  E_DF_DUPLICATE_DECLARATION=101
- Ability constraints: K: copy + drop + store; V: store (V: key rejected
  at runtime in M2 native since Move signatures cannot express "store !key")
- Compiled to 492-byte dynamic_field.mv via tools/move-compile

setu-move-vm/build.rs:
- stdlib modules 14 -> 15 (added "dynamic_field"); build_stdlib.sh sync
  check confirmed green

setu-validator/src/task_preparer/single.rs:954 + setu-enclave/src/mock/mod.rs:934:
- Ownership::ObjectOwner(_) match arm comment clarified from the stale
  "/* Move runtime handles */" to explicit DF semantics: "Owned by another
  object (e.g. a dynamic field entry). Sender authorization is proxied
  through the parent." No code behavior change.

Validation:
- cargo check -p setu-types: clean
- cargo test -p setu-types --lib: 128 passed / 0 failed (incl. 11 new)
- cargo check -p setu-move-vm: clean
- bash scripts/build_stdlib.sh: 15 modules, build.rs in sync
- bash ai/scripts/validate.sh --quick: 2/2 passed (compile + architecture)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant